1. 组件化

组件实际上是可以复用的 Vue 实例,它们与 new Vue 接收相同的选项,例如 datacomputedmethods 以及生命周期钩子等。
何谓复用?如果把页面看作是组件的容器,那么导航栏、搜索框其实都是可以复用的组件。作为对可重用代码的封装,它们自身具有独立的数据和逻辑。

前端组件化的核心思路就是将一个巨大复杂的东西拆分成若干个小东西(组件),这些组件可以自由组合、替换和删除,同时不影响整个应用的运行,这就是组件化开发。

组件化的好处是:

  • 提高开发效率
  • 方便重复使用,简化调试步骤,方便单元测试
  • 提升整个项目的可维护性,方便团队成员的协同开发
  • 高内聚(功能必须是完整的)、低耦合(解耦业务逻辑和数据)

2. 创建组件

2.1 全局组件

全局组件在 new Vue 之前创建,创建之后可用于所有根实例的模板中。
2.x 之前全局组件的创建过程如下:

let obj = Vue.extend({/*option*/})   // 创建组件构造器对象
Vue.component(TagName,obj)           // 注册组件

2.x 之后语法糖的写法如下:

Vue.component("TagName",{/*option*/})   // 同时创建并注册组件

2.2 局部组件

更多的是创建局部组件,让其只能在当前所处的 Vue 实例的模板中使用。

var obj = {/*option*/}
const app = new Vue({
  el:'#app',
  components:{
    "aaa":obj
  }
})

如果需要创建父子组件,那么可以这样写:

var son = {/*option*/}

var parent = {
  template:`<div><h2>something</h2><bbb></bbb></div>`,
  components:{
    "bbb":son
  }
}

const app = new Vue({
  el:'#app',
  components:{
    "aaa":parent
  }
})

之后在 dom 中书写 <aaa></aaa> ,会发现父子组件都可以渲染,但是单独书写 <bbb></bbb> 则无法渲染子组件,这是因为子组件是在父组件中注册的,因此它只能在父组件的模板中使用。

2.3 模板抽离

上面的 <div><h2>something</h2><bbb></bbb></div> 可以单独抽离出来放在一个有 id 的 <template></template> 中,之后直接 #id 引用该模板即可。

<template id="temp">
  <div>
    <h2>something</h2>
    <bbb></bbb>
  </div>
</template>

// 改写如下:
var parent = {
  template:`<div><h2>something</h2><bbb></bbb></div>`,
  components:{
    bbb:"#temp"
  }
}

注意:每个组件都必须有且仅能有一个根元素,这意味着组件所有的内容必须包裹在一个最外层元素中。

3. 组件的命名

组件创建后,直接在 dom 中书写组件名即可使用组件。但是组件的命名有一定的规则。
定义组件名的方式有两种:

(1) 使用 kebab-case(字母全小写+连字符),例如:

Vue.component('my-component', { /*option*/ })

使用时也必须是 kebab-case。即 <my-component></my-component>,否则会报错。

(2) 使用 PascalCase(字母全大写),例如:

Vue.component('MyComponent',{/* option*/})

如果是在父组件模板(模板没有抽离到 HTML 中的)中使用,则允许 kebab-case 和 PascalCase 两种方式,即 <MyComponent></MyComponent> 或者 <my-component></my-component> 都是允许的;但是如果直接在 DOM (非字符串的模板)中,则只能使用相应的 kebab-case,否则会报错。

我们来看一个例子:

上图中我们创建了父子组件,其中子组件采用 PascalCase 命名,之后在父组件模板中引用子组件时,发现不管是 kebab-case 命名还是 PascalCase 命名都是可以成功渲染的。

再来看第二张图:

我们创建了 HisCpn 组件,之后直接在 DOM 中引用(没有转换为 kebab-case 命名),结果报错了;同样的,我们创建了 cpn1MyCpn 父子组件,之后直接在 DOM 中引用,发现转换为 kebab-case 命名
的子组件可以正常渲染,而仍然采用 PascalCase 命名的子组件则报错了,因为前面我们说过:如果直接在 DOM (非字符串的模板)中,则只能使用相应的 kebab-case,否则会报错。

注意: PascalCase 每个单词首字母都大写,而 camelCase 即驼峰式命名第一个单词首字母不大写。

4. 为什么组件的 data 必须是函数?

另外还有一个需要注意的地方是,根实例的 data 是对象,但是组件的 data 却是函数。
这是因为组件是可复用的,每次使用一次 <my-component></my-component> 就会创建一个组件实例,如果定义组件时 data 依然返回的是对象,那么一个组件数据的更改将会同步影响到其它组件,因为它们共享一个 data 对象。如下图所示,我们只操作了一个组件,但三个组件数据都同步改变:

相反,如果 data 是函数,那么每次函数执行时都会开辟新的内存空间,创建并返回一个新的对象副本,这使得每个实例都有自己的 data 对象,实例互相之间不影响。如下图所示: